package com.ponhu.ea2.stockcenter.provider.support;

import com.alibaba.fastjson.JSON;
import com.ponhu.ea2.stockcenter.dto.WMSEventInfoDTO;
import com.ponhu.ea2.stockcenter.dto.WMSInventoryInfo;
import com.ponhu.ea2.stockcenter.enums.ExchangeActionTypeEnum;
import com.ponhu.ea2.stockcenter.provider.constants.CommonConstant;
import com.ponhu.ea2.stockcenter.provider.support.exception.StockRuntimeException;
import com.ponhu.ea2.stockcenter.provider.support.service.RabbitMQService;
import com.ponhu.ea2.stockcenter.provider.support.service.RedisSupportService;
import com.ponhu.ea2.stockcenter.provider.utils.CommonUtils;
import com.ponhu.ea2.stockcenter.provider.utils.DingTalkHandler;
import com.ponhu.ea2.stockcenter.provider.utils.RedisUtils;
import com.ponhu.ea2.stockcenter.vo.DingTalkContentVO;
import com.ponhu.ea2.stockcenter.vo.ReturnResponseBean;
import io.netty.util.concurrent.FastThreadLocal;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.ponhu.ea2.stockcenter.enums.ResponseCodeEnum.PARAMS_INCORRECT_EXCEPTION_900003;
import static com.ponhu.ea2.stockcenter.enums.ResponseCodeEnum.STOCK_WAREHOUSE_EXCEPTION_900106;

@Slf4j
@Aspect
@Order(1)
@Component
public class LogServiceAop {

    @Autowired
    private FastThreadLocal<String> traceIdLocal;

    @Autowired
    private DingTalkHandler dingTalkHandler;

    @Autowired
    private RabbitMQService rabbitMQService;

    @Autowired
    private RedisSupportService redisSupportService;

    @Pointcut("execution(* com.ponhu.ea2.stockcenter.provider.service.rpc.impl.*.*(..))")
    private void annotationAspect() {

    }

    @Around("annotationAspect()")
    public Object doAroundService(ProceedingJoinPoint proceedingJoinPoint) {
        String traceId = CommonUtils.getUUID();
        traceIdLocal.set(traceId);

        String className = proceedingJoinPoint.getTarget().getClass().getSimpleName();
        String methodName = proceedingJoinPoint.getSignature().getName();
        String servletPath = String.format("dubbo facade interface %s.%s", className, methodName);
        Object[] args = proceedingJoinPoint.getArgs();

        try {
            log.info("{} ===>> [{}] ===>> 入参={}", servletPath, traceId, JSON.toJSONString(args));

            Object proceed = proceedingJoinPoint.proceed();

            log.info("{} ===>> [{}] ===>> 请求成功：{}", servletPath, traceId, JSON.toJSONString(proceed));

            saleStockChangeSendMsg(className, methodName, args);

            return proceed;
        } catch (Throwable throwable) {
            if (throwable instanceof StockRuntimeException) {
                StockRuntimeException ex = (StockRuntimeException) throwable;
                log.error("{} ===>> [{}] ===>> 入参={} ===>> 业务异常：[code={}, msg={}]",
                        servletPath, traceId, JSON.toJSONString(proceedingJoinPoint.getArgs()), ex.getCode(), ex.getMessage());

                if (methodName.equals("receiveWMSEventHandler")) {
                    WMSEventInfoDTO wmsEventInfoDTO = (WMSEventInfoDTO) proceedingJoinPoint.getArgs()[1];

                    // 事件处理异常则将其关联货品编码加入Redis缓存待定时任务修复处理
                    CompletableFuture.runAsync(() -> {
                        if (ex.getCodeEnum() == STOCK_WAREHOUSE_EXCEPTION_900106) return;

                        List<String> inventoryCodes = wmsEventInfoDTO.getInventoryInfoList()
                                .parallelStream().map(WMSInventoryInfo::getInventoryCode).collect(Collectors.toList());
                        redisSupportService.rSetAddAll(CommonConstant.WMS_EVENT_HANDLER_ERROR, inventoryCodes);
                    });

                    // 出库事件业务异常后对其事件进行缓存待后续入库事件将其激活后再次执行
                    CompletableFuture.runAsync(() -> {
                        if (ex.getCodeEnum() == STOCK_WAREHOUSE_EXCEPTION_900106) return;

                        List<ExchangeActionTypeEnum> outEnumList = Arrays.asList(
                                ExchangeActionTypeEnum.SALES_RETURN_CANCLE_OUT,
                                ExchangeActionTypeEnum.PURCHASE_CANCEL_OUT,
                                ExchangeActionTypeEnum.PURCHASE_RETURN_OUT,
                                ExchangeActionTypeEnum.SALES_OUT);
                        if (outEnumList.contains(wmsEventInfoDTO.getActionType()) && wmsEventInfoDTO.isFirstFrequency()) {
                            wmsEventInfoDTO.addFrequency();
                            wmsEventInfoDTO.getInventoryInfoList().stream().forEach(info -> {
                                String redisKey = RedisUtils.getRedisKey(CommonConstant.WMS_OUT_EVENT_HANDLER_ERROR,
                                        wmsEventInfoDTO.getOutWarehouseCode(), info.getInventoryCode());
                                redisSupportService.bucketSet(redisKey, JSON.toJSONString(wmsEventInfoDTO), 24, TimeUnit.HOURS);

                                log.error("{} ===>> [{}] ===>> [出库事件处理异常，对出库事件进行Redis缓存] ===>> 入参={}",
                                        servletPath, traceId, JSON.toJSONString(wmsEventInfoDTO));
                            });
                        } else { // inEnumList 或 (outEnumList && Redis缓存处理)

                            // 业务异常则发生钉钉报警
                            CompletableFuture.runAsync(() -> {
                                DingTalkContentVO dingTalkContentVO = new DingTalkContentVO();
                                dingTalkContentVO.setPath(servletPath);
                                dingTalkContentVO.setTraceId(traceId);
                                dingTalkContentVO.setRequestParams(JSON.toJSONString(wmsEventInfoDTO));
                                dingTalkContentVO.setExceptionType("业务异常");
                                dingTalkContentVO.setExceptionCode(String.valueOf(Objects.toString(ex.getCode(), "")));
                                dingTalkContentVO.setExceptionMsg(ex.getMessage());

                                dingTalkHandler.sendMsg(dingTalkContentVO);
                            });
                        }
                    });

                } else { // 业务异常则发生钉钉报警
                    CompletableFuture.runAsync(() -> {
                        DingTalkContentVO dingTalkContentVO = new DingTalkContentVO();
                        dingTalkContentVO.setPath(servletPath);
                        dingTalkContentVO.setTraceId(traceId);
                        dingTalkContentVO.setRequestParams(JSON.toJSONString(proceedingJoinPoint.getArgs()));
                        dingTalkContentVO.setExceptionType("业务异常");
                        dingTalkContentVO.setExceptionCode(String.valueOf(Objects.toString(ex.getCode(), "")));
                        dingTalkContentVO.setExceptionMsg(ex.getMessage());

                        dingTalkHandler.sendMsg(dingTalkContentVO);
                    });
                }

                return ReturnResponseBean.error(ex.getCode(), ex.getMessage());
            }

            //校验框架异常处理
            if (throwable instanceof ConstraintViolationException) {
                ConstraintViolationException exs = (ConstraintViolationException) throwable;

                Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
                String msg = violations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(";"));
                log.error("{} ===>> [{}] ===>> 入参={} ===>> 参数校验异常：[msg={}]",
                        servletPath, traceId, JSON.toJSONString(proceedingJoinPoint.getArgs()), msg);

                DingTalkContentVO dingTalkContentVO = new DingTalkContentVO();
                dingTalkContentVO.setPath(servletPath);
                dingTalkContentVO.setTraceId(traceId);
                dingTalkContentVO.setRequestParams(JSON.toJSONString(proceedingJoinPoint.getArgs()));
                dingTalkContentVO.setExceptionType("参数校验异常");
                dingTalkContentVO.setExceptionCode("");
                dingTalkContentVO.setExceptionMsg(msg);

                dingTalkHandler.sendMsg(dingTalkContentVO);

                return ReturnResponseBean.error(PARAMS_INCORRECT_EXCEPTION_900003.getCode(), msg);
            }

            throwable.printStackTrace();
            String throwableMsg = "";
            if (throwable.getStackTrace() != null && throwable.getStackTrace().length > 0) {
                throwableMsg = throwable.getStackTrace()[0].toString();
            }
            log.error("{} ===>> [{}] ===>> 入参={} ===>> 系统异常：[msg={}, causeby={}]",
                    servletPath, traceId, JSON.toJSONString(proceedingJoinPoint.getArgs()), throwable.getMessage(), throwableMsg);

            // 程序异常则发生钉钉报警
            DingTalkContentVO dingTalkContentVO = new DingTalkContentVO();
            dingTalkContentVO.setPath(servletPath);
            dingTalkContentVO.setTraceId(traceId);
            dingTalkContentVO.setRequestParams(JSON.toJSONString(proceedingJoinPoint.getArgs()));
            dingTalkContentVO.setExceptionType("参数校验异常");
            dingTalkContentVO.setExceptionCode("");
            dingTalkContentVO.setExceptionMsg(throwable.getMessage() + " " + throwableMsg);

            dingTalkHandler.sendMsg(dingTalkContentVO);

            return ReturnResponseBean.error("服务器开小差了:" + Objects.toString(throwable.getMessage(), "异常内容为空"));
        }
    }

    public void saleStockChangeSendMsg(String className, String methodName, Object[] args) {
        CompletableFuture.runAsync(() -> {
            List<String> inventoryCodes = null;
            if (className.equals("StockForGoodsCenterServiceImpl")) {
                List<String> methodList1 = Arrays.asList("createAndInitUnRealStockData", "createAndInitRealStockData");
                if (methodList1.contains(methodName)) {
                    inventoryCodes = Arrays.asList((String) args[2]);
                }
                List<String> methodList2 = Arrays.asList("addOneVirtualSalesStockData", "deleteVirtualSalesStockData");
                if (methodList2.contains(methodName)) {
                    inventoryCodes = (List<String>) args[0];
                }
            }

            rabbitMQService.saleStockChangeSendMsg(inventoryCodes);
        });
    }

}
